//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with this program.  If not, see http://www.gnu.org/licenses/.
// 

#include "Switch.h"

Define_Module(Switch);

void Switch::initialize()
{
	sizeofQ=100;
	id=par("idSwitch");
	Q_eq=par("Q_EQ");
	W=2;
	for(int i=0;i<5;i++)
	{
		congP[i].qlen=0;
		congP[i].qlen_old=0;
		congP[i].time_to_mark=Mark_Table(0);
	}
	time_interval.setRangeAutoUpper(0,100,1.5);
	que.setRangeAuto(100,1.5);
}

void Switch::handleMessage(cMessage *msg)
{

	int ethDst;
	Eth_pck *recEth;
	recEth=dynamic_cast<Eth_pck *>(msg);

	if(recEth->getDstID()>3)
	{
		ethDst=4;
	}else
	{
		ethDst=recEth->getDstID();
	}



	if(msg->isSelfMessage())
	{
		if(id==0)
		{
			Schannel=gate("out",ethDst)->getChannel();
			if(Schannel->isBusy()){
				//scheduleAt(Schannel->getTransmissionFinishTime(),msg);
				cancelAndDelete(msg);

			}else
			{
				if(waitTosend[ethDst].length()!=0){
				send(check_and_cast<cMessage *>(waitTosend[ethDst].pop()),"out",ethDst);
				scheduleAt(Schannel->getTransmissionFinishTime(),msg);

				if(ethDst==4)
				{
					que.collect(waitTosend[4].length());
					que_vector.record(waitTosend[4].length());
				}

				}else
				{
					cancelAndDelete(msg);
				}
			}


		}else
		{
			cancelAndDelete(msg);
		}

	}else
	{

		if(id==0)
		{
			CongestionPoint(msg);

			if(waitTosend[ethDst].length()<sizeofQ)
			{
				if(ethDst==4)
				{
					que.collect(waitTosend[4].length());
					que_vector.record(waitTosend[4].length());
				}

				Schannel=gate("out",ethDst)->getChannel();

				if(Schannel->isBusy()){
					note =new Eth_pck();
					note->setDstID(ethDst);
					waitTosend[ethDst].insert(msg);
					scheduleAt(Schannel->getTransmissionFinishTime(),note);
				}else{
					send(msg,"out",ethDst);
				}
			}
			else //Que is full-> loosing msg
			{
				delete(msg);
			}


		}else //id of switch is "1"
		{
			delete(msg);
		}
	}

}


void Switch::CongestionPoint(cMessage *msg)
{

	int Fb;
	int qntz_Fb;
	int generate_fb_frame;
	int next_period;
	int ethDst;
	double rnd;
	double time_to_mark;
	Eth_pck *recEth;
	rnd =par("random" );
	recEth=dynamic_cast<Eth_pck *>(msg);


	//Check which que we're at
	if(recEth->getDstID()>3)
	{
		ethDst=4;
	}else
	{
		ethDst=recEth->getDstID();
	}

	this->congP[ethDst].qlen=this->waitTosend[ethDst].length();
	time_to_mark=this->congP[ethDst].time_to_mark;

	Fb= (Q_eq -this->congP[ethDst].qlen)
										- W*(this->congP[ethDst].qlen -this->congP[ethDst].qlen_old);
	if(Fb< -Q_eq*(2*W+1))
	{
		Fb= -Q_eq*(2*W+1);
	}else if(Fb>0)
	{
		Fb=0;
	}

	qntz_Fb=sixBit(Fb);
	generate_fb_frame=0;

	time_to_mark-= recEth->getByteLength();
	if(time_to_mark<0)
	{
		if(qntz_Fb>0)
		{
			generate_fb_frame=1;
		}
		this->congP[ethDst].qlen_old= this->congP[ethDst].qlen;
		next_period=Mark_Table(qntz_Fb);
		time_to_mark=rnd*next_period;
	}
	this->congP[ethDst].time_to_mark=time_to_mark;

	if(generate_fb_frame==1)
	{

		FBFrame *sendFb = new FBFrame();
		sendFb->setKind(2);
		sendFb->setDA(recEth->getSrcID());
		sendFb->setSA(id);
		sendFb->setFlowid(0);
		sendFb->setFb(qntz_Fb);
		sendFb->setQoff(Q_eq-this->congP[ethDst].qlen);
		sendFb->setQdelta(this->congP[ethDst].qlen-this->congP[ethDst].qlen_old);
		//this->waitTosend[sendFb->getDA()].insert(sendFb);
		if(waitTosend[sendFb->getDA()].length()>0){
			this->waitTosend[sendFb->getDA()].insertBefore(waitTosend[sendFb->getDA()].front(),sendFb);
			EV<<"fb in front"<<endl;
		}else{
			this->waitTosend[sendFb->getDA()].insert(sendFb);
			EV<<"fb attached"<<endl;
		}



		Schannel=gate("out",sendFb->getDA())->getChannel();
		note =new Eth_pck();
		note->setDstID(sendFb->getDA());
		if(Schannel->isBusy())
		{
			scheduleAt(Schannel->getTransmissionFinishTime(),note);
		}else
		{
			scheduleAt(simTime(),note);
		}


	}

}


int Switch::sixBit(int toQuan)
{
	unsigned int temp = 1;
	unsigned int qntzFb = 0;
	if (toQuan == 0)
		return 0;
	qntzFb = -1*toQuan;
	if(qntzFb>63)
	{
	temp = (temp << 31);
	while(!(qntzFb & temp))
		qntzFb = (qntzFb << 1);
	temp = (temp >> 5);
	return qntzFb/temp;
	}else
	{
		return qntzFb;
	}
}


double Switch:: Mark_Table(int qntz_Fb)
{
	switch(qntz_Fb/8){

	case 0:return 150000;
	case 1:return 75000;
	case 2:return 50000;
	case 3:return 37500;
	case 4:return 30000;
	case 5:return 25000;
	case 6:return 21500;
	case 7:return 18500;
	default: return 18500;

	}

}
void Switch::finish()
{

}
